/*
 * Copyright (c) 1994, 2004, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package jdk.io;

import java.io.IOException;
import java.io.OutputStream;
import java.io.FilterOutputStream;
import java.io.UTFDataFormatException;

/**
 * 数据输出流允许应用程序以可移植的方式将原始 Java 数据类型写入输出流。
 * 应用程序可以使用数据输入流来读取数据。
 *
 * @author  Arthur van Hoff
 * @see     java.io.DataInputStream
 * @since   JDK1.0
 */
public
class DataOutputStream extends FilterOutputStream implements DataOutput {
    /**
     * 已写入输出流的字节数。
     */
    protected int written;

    /**
     * 由 writeUTF 使用的字节数组。
     */
    private byte[] bytearr;

    /**
     * 创建一个新的数据输出流，以将数据写入指定的底层输出流。
     *
     * @param   out   要写入的底层输出流。
     */
    public DataOutputStream(OutputStream out) {
        super(out);
    }

    /**
     * 增加已写入计数 {@code written}，直到 {@code Integer.MAX_VALUE}。
     */
    private void incCount(int value) {
        int temp = written + value;
        if (temp < 0) {
            temp = Integer.MAX_VALUE;
        }
        written = temp;
    }

    /**
     * 将指定字节（参数 {@code b} 的低 8 位）写入底层输出流。
     * 如果发生 I/O 错误，则抛出 {@code IOException}。
     *
     * @param      b   要写入的字节。
     * @exception  IOException  如果发生 I/O 错误。
     * @see        java.io.FilterOutputStream#out
     */
    public synchronized void write(int b) throws IOException {
        out.write(b);
        incCount(1);
    }

    /**
     * 从指定的字节数组写入 {@code len} 字节，从偏移量 {@code off} 开始，到底层输出流。
     * 如果发生 I/O 错误，则抛出 {@code IOException}。
     *
     * @param      b     要写入的数据。
     * @param      off   数据中的起始偏移量。
     * @param      len   要写入的字节数。
     * @exception  IOException  如果发生 I/O 错误。
     * @see        java.io.FilterOutputStream#out
     */
    public synchronized void write(byte b[], int off, int len)
        throws IOException
    {
        out.write(b, off, len);
        incCount(len);
    }

    /**
     * 刷新此数据输出流。这将强制将任何缓冲的输出字节写出。
     *
     * @exception  IOException  如果发生 I/O 错误。
     * @see        java.io.FilterOutputStream#out
     */
    public void flush() throws IOException {
        out.flush();
    }

    /**
     * 写入布尔值作为 1 字节值。值 {@code true} 写出为值 {@code (byte)1}；
     * 值 {@code false} 写出为值 {@code (byte)0}。
     *
     * @param      v   要写入的布尔值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeBoolean(boolean v) throws IOException {
        out.write(v ? 1 : 0);
        incCount(1);
    }

    /**
     * 将 {@code byte} 作为 1 字节值写出。
     *
     * @param      v   要写入的 {@code byte} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeByte(int v) throws IOException {
        out.write(v);
        incCount(1);
    }

    /**
     * 将 {@code short} 作为 2 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code short} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeShort(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 将 {@code char} 作为 2 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code char} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeChar(int v) throws IOException {
        out.write((v >>> 8) & 0xFF);
        out.write((v >>> 0) & 0xFF);
        incCount(2);
    }

    /**
     * 将 {@code int} 作为 4 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code int} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }

    private byte writeBuffer[] = new byte[8];

    /**
     * 将 {@code long} 作为 8 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code long} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeLong(long v) throws IOException {
        writeBuffer[0] = (byte)(v >>> 56);
        writeBuffer[1] = (byte)(v >>> 48);
        writeBuffer[2] = (byte)(v >>> 40);
        writeBuffer[3] = (byte)(v >>> 32);
        writeBuffer[4] = (byte)(v >>> 24);
        writeBuffer[5] = (byte)(v >>> 16);
        writeBuffer[6] = (byte)(v >>>  8);
        writeBuffer[7] = (byte)(v >>>  0);
        out.write(writeBuffer, 0, 8);
        incCount(8);
    }

    /**
     * 将 {@code float} 作为 4 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code float} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    /**
     * 将 {@code double} 作为 8 字节值，高字节在前。
     *
     * @param      v   要写入的 {@code double} 值。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    /**
     * 将字符串写入输出流。对于字符串中的每个字符，将写出低 8 位。
     * 如果 {@code s} 为 null，则抛出 {@code NullPointerException}。
     * 如果 {@code s.length} 为 0，则不写入任何字节。否则，字符 {@code s[0]} 到
     * {@code s[s.length-1]} 按顺序写入输出流。
     *
     * @param      s   要写入的 {@code String}。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeBytes(String s) throws IOException {
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            out.write((byte)s.charAt(i));
        }
        incCount(len);
    }

    /**
     * 将字符串写入输出流。对于字符串中的每个字符，将写出 2 字节，高字节在前。
     * 如果 {@code s} 为 null，则抛出 {@code NullPointerException}。
     * 如果 {@code s.length} 为 0，则不写入任何字节。否则，字符 {@code s[0]} 到
     * {@code s[s.length-1]} 按顺序写入输出流。
     *
     * @param      s   要写入的 {@code String}。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeChars(String s) throws IOException {
        int len = s.length();
        for (int i = 0 ; i < len ; i++) {
            int v = s.charAt(i);
            out.write((v >>> 8) & 0xFF);
            out.write((v >>> 0) & 0xFF);
        }
        incCount(len * 2);
    }

    /**
     * 使用修改版 UTF-8 编码以机器无关的方式将字符串写入输出流。
     * <p>
     * 首先，通过 {@code writeShort} 方法将两个字节写入输出流，以与字节序列长度相同的顺序，
     * 以给出要遵循的字节数。此值是实际写入输出流的字节数，而不是字符串的长度。
     * 根据字符串 {@code str} 中的字符序列，使用修改版 UTF-8 编码写出字符序列。
     * 每个字符在字符串中由 1、2 或 3 字节组成。
     *
     * @param      str   要写入的 {@code String}。
     * @exception  IOException  如果发生 I/O 错误。
     */
    public final void writeUTF(String str) throws IOException {
        writeUTF(str, this);
    }

    /**
     * 返回已写入此数据输出流的当前字节数。
     *
     * @return  已写入此数据输出流的当前字节数。
     */
    public final int size() {
        return written;
    }

    /**
     * 使用修改版 UTF-8 编码以机器无关的方式将字符串写入输出流。
     * <p>
     * 对于字符串中的每个字符，将写出 1、2 或 3 字节。如果字符 {@code c}
     * 在范围 {@code \u0001} 到 {@code \u007f} 内，则它由 1 字节表示：
     * <pre>{@code (byte)c}</pre>
     * 如果字符 {@code c} 是 {@code \u0000} 或在范围 {@code \u0080} 到 {@code \u07ff} 内，
     * 则它由 2 字节表示，按所示顺序写入：
     * <pre>{@code (byte)(0xc0 | (0x1f & (c >> 6))) (byte)(0x80 | (0x3f & c))}</pre>
     * 如果字符 {@code c} 在范围 {@code \u0800} 到 {@code \uffff} 内，
     * 则它由 3 字节表示，按所示顺序写入：
     * <pre>{@code (byte)(0xe0 | (0x0f & (c >> 12))) (byte)(0x80 | (0x3f & (c >> 6))) (byte)(0x80 | (0x3f & c))}</pre>
     * <p>
     * 首先，计算表示字符串 {@code str} 所有字符所需的总字节数。
     * 如果此数字大于 {@code 65535}，则抛出 {@code UTFDataFormatException}。
     * 否则，此长度通过 {@code writeShort} 方法写入输出流；
     * 之后，写出字符串 {@code str} 中每个字符的 1、2 或 3 字节表示。
     * <p>
     * 此方法写入的字节可以由接口 {@code DataInput} 的 {@code readUTF} 方法读取，
     * 然后返回等于 {@code str} 的 {@code String}。
     *
     * @param      str   要写入的字符串。
     * @param      out   要写入的数据输出流。
     * @return     写入输出流的字节数。
     * @exception  IOException  如果发生 I/O 错误。
     */
    static int writeUTF(String str, DataOutput out) throws IOException {
        int strlen = str.length();
        int utflen = 0;
        int c, count = 0;

        /* 使用 strlen 至少为 2 用于已编码长度，加上 strlen 用于已编码字符 */
        for (int i = 0; i < strlen; i++) {
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                utflen++;
            } else if (c > 0x07FF) {
                utflen += 3;
            } else {
                utflen += 2;
            }
        }

        if (utflen > 65535)
            throw new UTFDataFormatException(
                "编码字符串过长：" + utflen + " 字节");

        byte[] bytearr = null;
        if (out instanceof DataOutputStream) {
            DataOutputStream dos = (DataOutputStream)out;
            if(dos.bytearr == null || (dos.bytearr.length < (utflen+2)))
                dos.bytearr = new byte[(utflen*2) + 2];
            bytearr = dos.bytearr;
        } else {
            bytearr = new byte[utflen+2];
        }

        bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
        bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);

        int i=0;
        for (i=0; i<strlen; i++) {
           c = str.charAt(i);
           if (!((c >= 0x0001) && (c <= 0x007F))) break;
           bytearr[count++] = (byte) c;
        }

        for (;i < strlen; i++){
            c = str.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                bytearr[count++] = (byte) c;

            } else if (c > 0x07FF) {
                bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                bytearr[count++] = (byte) (0x80 | ((c >>  6) & 0x3F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            } else {
                bytearr[count++] = (byte) (0xC0 | ((c >>  6) & 0x1F));
                bytearr[count++] = (byte) (0x80 | ((c >>  0) & 0x3F));
            }
        }
        out.write(bytearr, 0, utflen+2);
        return utflen + 2;
    }
}
